package cn.z2huo.demo.log.aop.web.aspect;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import cn.z2huo.core.enums.YesNoEnum;
import cn.z2huo.demo.log.aop.web.annotation.Log;
import cn.z2huo.demo.log.aop.web.enums.AppEnum;
import cn.z2huo.demo.log.aop.web.service.OperateLogService;
import cn.z2huo.demo.model.dataobject.system.OperateLogDO;
import com.alibaba.fastjson2.JSONObject;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * <p>
 *
 * @author z2huo
 */
@Slf4j
@Aspect
@Component
@Order(10)
public class LogAspect {

    @Autowired
    private OperateLogService operateLogService;

    @Pointcut("@annotation(log)")
    public void annotationLogPointCut(Log log) {
    }

    @Pointcut("execution(public * cn.z2huo.demo.log.aop.web.controller..*.*(..))")
    public void controllerLogPointCut() {
    }

    @Around(value = "annotationLogPointCut(log)", argNames = "point,log")
    public Object aroundLog(ProceedingJoinPoint point, Log log) throws Throwable {

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = Objects.requireNonNull(attributes).getRequest();

        LocalDateTime now = LocalDateTime.now();

        OperateLogDO operateLogDO = new OperateLogDO().setComCode("").setComName("")
                .setOperatorCode("").setOperatorName("")
                .setFiscalYear(now.getYear())
                .setAppId(AppEnum.USER_CENTER_APP.getValue())
                .setOperateModule(log.module().getValue())
                .setOperateTitle(log.title())
                .setOperateIp(JakartaServletUtil.getClientIP(request))
                .setOperateUrl(request.getRequestURI())
                .setOperateMethod(request.getMethod())
                .setOperateTime(now)
                .setOperateParam(JSONObject.toJSONString(this.getNameAndValue(point)));

        try {
            long start = System.currentTimeMillis();
            Object object = point.proceed();
            long end = System.currentTimeMillis();

            operateLogDO.setOperateStatus(YesNoEnum.YES.getValue())
                    .setOperateCost(end - start)
                    .setOperateResult(object.toString());

            return object;
        } catch (Throwable e) {
            operateLogDO.setOperateStatus(YesNoEnum.NO.getValue()).setOperateErrorMsg(e.getMessage());
            throw e;
        } finally {
            operateLogService.insertLogAsync(operateLogDO);
        }
    }

//    @AfterReturning(value = "annotationLogPointCut(log)", returning = "result", argNames = "log,result")
    public void returnLog(Log log, Object result) {

    }

//    @AfterThrowing(value = "annotationLogPointCut(log)", throwing = "exception", argNames = "log,exception")
    public void throwExceptionLog(Log log, Exception exception) {

    }

    /**
     * 获取方法参数名和参数值
     */
    private Map<String, Object> getNameAndValue(ProceedingJoinPoint joinPoint) {

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        String[] names = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();

        if (ArrayUtil.isEmpty(names) || ArrayUtil.isEmpty(args)) {
            return Map.of();
        }
        if (names.length != args.length) {
            log.warn("{} 方法参数名和参数值数量不一致", methodSignature.getName());
            return Map.of();
        }
        Map<String, Object> map = new HashMap<>();
        for (int i = 0; i < names.length; i++) {
            map.put(names[i], args[i]);
        }
        return map;
    }

}
